package ru.ifmo.network.http;

import com.sun.xml.internal.messaging.saaj.util.ByteOutputStream;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.URLDecoder;
import java.nio.charset.Charset;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @author avhaliullin
 */
public class HttpServer {
    private ServerSocket serverSocket;
    private final int port;
    private static final Pattern URL_PATTERN = Pattern.compile(
            "^(/([^?#]+)?)" + //path
                    "(\\?([^#]+)?)?" + //params
                    "(#(.+)?)?$" //anchor
    );

    public HttpServer(int port) throws IOException {
        this.port = port;
    }

    public void start() throws IOException {
        serverSocket = new ServerSocket(port);
        while (!Thread.interrupted()) {
            new Thread(new ClientHandler(serverSocket.accept())).start();
        }
    }

    private class ClientHandler implements Runnable {
        private final Socket socket;

        public ClientHandler(Socket socket) {
            this.socket = socket;
        }

        private void error(int code) throws IOException {
            PrintWriter out = new PrintWriter(new OutputStreamWriter(socket.getOutputStream(), Charset.forName("utf8")));
            out.println(String.format("HTTP/1.1 %d", code));
            out.flush();
            socket.close();
        }

        public void run() {
            try {
                InputStream in = socket.getInputStream();
                BufferedReader reader = new BufferedReader(new InputStreamReader(in));
                String s = reader.readLine();
                if (!s.startsWith("GET")) {
                    error(400);
                    return;
                }
                s = s.substring(4);
                StringTokenizer st = new StringTokenizer(s);
                if (!st.hasMoreTokens()) {
                    error(400);
                    return;
                }
                s = st.nextToken();
                String path;
                Matcher m = URL_PATTERN.matcher(s);
                List<Param> paramList = new ArrayList<Param>();
                String name = null;
                if (m.find()) {
                    path = m.group(1);
                    String params = m.group(4);
                    if (params != null && !params.isEmpty()) {
                        Map<String, String> paramsMap = new HashMap<String, String>();
                        boolean parsingName = true;
                        StringBuilder currentParamName = new StringBuilder();
                        StringBuilder currentParamValue = new StringBuilder();
                        for (int i = 0; i < params.length(); i++) {
                            char c = params.charAt(i);
                            if (parsingName) {
                                if (c != '=' && c != '&') {
                                    currentParamName.append(c);
                                } else {
                                    if (c == '=') {
                                        parsingName = false;
                                    } else {
                                        paramsMap.put(decode(currentParamName.toString()), null);
                                        paramList.add(new Param(currentParamName.toString(), null));
                                        currentParamName = new StringBuilder();
                                        currentParamValue = new StringBuilder();
                                    }
                                }
                            } else {
                                if (c != '&') {
                                    currentParamValue.append(c);
                                } else {
                                    paramsMap.put(decode(currentParamName.toString()), decode(currentParamValue.toString()));
                                    paramList.add(new Param(currentParamName.toString(), currentParamValue.toString()));
                                    currentParamName = new StringBuilder();
                                    currentParamValue = new StringBuilder();
                                    parsingName = true;
                                }
                            }
                        }
                        if (currentParamName.length() > 0) {
                            paramsMap.put(decode(currentParamName.toString()), decode(currentParamValue.toString()));
                            paramList.add(new Param(currentParamName.toString(), currentParamValue.toString()));
                        }
                        name = paramsMap.get("name");
                    }
                } else {
                    error(400);
                    return;
                }
                if (name == null) {
                    name = "world";
                }

                PrintWriter out = new PrintWriter(new OutputStreamWriter(socket.getOutputStream(), Charset.forName("utf8")));
                out.println("HTTP/1.1 200 ok");
                out.println("Content-Type: text/html; charset=UTF-8");

                StringBuilder table = new StringBuilder();
                if (!paramList.isEmpty()) {
                    table.append("<table border='1'><th>Name</th><th>Value</th>");
                    for (Param param : paramList) {
                        table.append(String.format("<tr><td>%1$s</td><td>%2$s</td></tr>", param.name, param.value));
                    }
                    table.append("</table>");
                }

                String toSend = String.format("<html><body><h1>Hello %1$s!</h1><h2>Path: %2$s</h2>%3$s</body></html>", name, decode(path), table.toString());
                ByteOutputStream byteOut = new ByteOutputStream();
                PrintWriter byteWriter = new PrintWriter(new OutputStreamWriter(byteOut));
                byteWriter.print(toSend);
                byteWriter.flush();
                byteWriter.close();
                out.println("Content-Length: " + byteOut.getCount());
                out.println();
                out.print(toSend);
                out.flush();
                socket.close();
            } catch (IOException e) {
                e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
            }
        }
    }

    public static void main(String[] args) throws IOException {
        new HttpServer(8080).start();
    }


    private String decode(String s) {
        try {
            return URLDecoder.decode(s, "utf8");
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
            return s;
        }
    }

    class Param {
        public final String name;
        public final String value;

        public Param(String name, String value) {
            this.name = decode(name);
            this.value = decode(value);
        }
    }
}

